// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: taekwon, xguo

#include "coin2/exchange/huobi_futures/order_v1/parser.h"

#include <memory>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>

#include "coin2/base/conversion.h"
#include "coin2/base/log.h"
#include "coin2/exchange/util/json_util.h"

namespace coin2::exchange::huobi_futures::order_v1 {

namespace {

using coin::proto::AccountBalance;
using coin::proto::AccountPosition;
using coin::proto::CancelOrderResponse;
using coin::proto::CurrencyBalance;
using coin::proto::CurrencyTransferBundle;
using coin::proto::CurrencyTransferElement;
using coin::proto::FillType;
using coin::proto::ProductFillBundle;
using coin::proto::ProductOrderBundle;
using coin::proto::ProductOrderElement;
using coin::proto::ProductPosition;
using coin::proto::SubmitOrderResponse;
using coin::proto::TransferRequestProto;
using coin2::exchange::base::order::IOrderContext;
using coin2::exchange::huobi::symbology::HuobiCurrency;
using coin2::exchange::huobi_futures::symbology::HuobiFuturesProduct;

using JsonValue = rapidjson::GenericValue<rapidjson::UTF8<>>;

inline std::string GetFillId(const std::string& id, const std::string& trade_id) {
  static char buf[128];
  std::snprintf(&buf[0], sizeof(buf), "%s-%s", id.c_str(), trade_id.c_str());
  return std::string(&buf[0]);
}

inline FillType GetFillType(const std::string& role) {
  FillType fill_type;
  if (role == "maker" || role == "Maker") {
    fill_type = FillType::MAKER_FILL_TYPE;
  } else if (role == "taker" || role == "Taker") {
    fill_type = FillType::TAKER_FILL_TYPE;
  } else {
    fill_type = FillType::UNKNOWN_FILL_TYPE;
  }
  return fill_type;
}

inline int64_t GetProcOrderId(const JsonValue& data) {
  int64_t proc_order_id = 0;
  if (data.HasMember("client_order_id")) {
    if (!data["client_order_id"].IsString()) {
      return proc_order_id;
    }
    proc_order_id = stoll(data["client_order_id"].GetString());
  }
  return proc_order_id;
}

inline coin::proto::OrderSide GetOrderSide(const JsonValue& data) {
  using coin::proto::OrderSide;
  std::string direction = data["direction"].GetString();
  std::string offset = data["offset"].GetString();
  OrderSide side;

  if (direction == "buy" && offset == "open") {
    side = OrderSide::BUY_OPEN_ORDER;
  } else if (direction == "buy" && offset == "close") {
    side = OrderSide::BUY_CLOSE_ORDER;
  } else if (direction == "sell" && offset == "open") {
    side = OrderSide::SELL_OPEN_ORDER;
  } else if (direction == "sell" && offset == "close") {
    side = OrderSide::SELL_CLOSE_ORDER;
  } else {
    side = OrderSide::UNKNOWN_ORDER_SIDE;
  }
  return static_cast<coin::proto::OrderSide>(side);
}

inline coin::proto::OrderType GetOrderType() { return coin::proto::OrderType::LIMIT_ORDER; }

inline std::string GetExchangeOrderId(const JsonValue& data) {
  if (data.HasMember("order_id_str")) {
    return data["order_id_str"].GetString();
  } else {
    return data["order_id"].GetString();
  }
}

inline std::string GetExchangeOrderErrorCode(const JsonValue& json) {
  if (json.HasMember("err_code")) {
    return json["err_code"].GetString();
  } else {
    return {};
  }
}

inline std::optional<int64_t> GetExchangeOrderErrorCodeV3(const JsonValue& json) {
  if (!json.IsObject() || !json.HasMember("code") || !json["code"].IsString()) {
    return std::nullopt;
  }

  return stoll(json["code"].GetString());
}

inline std::string GetExchangeOrderErrorMsg(const JsonValue& json) {
  if (json.HasMember("err_msg") && json["err_msg"].IsString()) {
    return json["err_msg"].GetString();
  } else {
    return {};
  }
}

inline std::optional<std::string> GetExchangeOrderErrorMsgV3(const JsonValue& json) {
  if (!json.IsObject() || !json.HasMember("msg") || !json["msg"].IsString()) {
    return std::nullopt;
  }

  return json["msg"].GetString();
}

inline coin::proto::OrderErrorCode GetOrderErrorCode(const JsonValue& json_error_code) {
  using coin::proto::OrderErrorCode;
  auto result = OrderErrorCode::ORDER_UNKNOWN_ERROR;

  int32_t error_code = 0;
  const auto* const str = json_error_code.GetString();
  if (std::strlen(str) > 0) {
    error_code = std::stoi(str);
  }

  if (error_code == 0) {
    result = OrderErrorCode::ORDER_NO_ERROR;
  } else if (error_code == 1032) {
    result = OrderErrorCode::API_LIMIT_ERROR;
  } else if (error_code == 1047) {
    result = OrderErrorCode::SUBMIT_ORDER_INSUFFICIENT_FUND;
  } else if (error_code == 1048) {
    result = OrderErrorCode::SUBMIT_ORDER_INSUFFICIENT_POSITION;
  } else if (error_code == 1051) {
    result = OrderErrorCode::ORDER_NOT_FOUND;
  } else if (error_code == 1061) {
    result = OrderErrorCode::ORDER_ON_THE_FLY;
  } else if (error_code == 1063) {
    result = OrderErrorCode::ORDER_FILLED_ALREADY;
  } else if (error_code == 1071) {
    result = OrderErrorCode::ORDER_CANCELED_ALREADY;
  }
  return result;
}

inline void ParseBatchResponse(
    std::unordered_map<int64_t, const JsonValue*>* index_to_response_map,
    const JsonValue& array) {
  for (const auto& response : array.GetArray()) {
    CHECK(response.HasMember("index"))
        << "response without index! response:" << JsonToString(array);

    int64_t index = stoll(response["index"].GetString());

    CHECK_EQ(index_to_response_map->count(index), 0)
        << "duplicate index:" << index << " json:" << JsonToString(array);

    index_to_response_map->emplace(index, &response);
  }
}

bool IsHuobiErrMessage(const JsonValue& doc) {
  return doc.HasMember("status") && strcmp(doc["status"].GetString(), "error") == 0;
}

bool IsHuobiErrMessageV3(const JsonValue& doc) {
  auto code = GetExchangeOrderErrorCodeV3(doc);
  auto msg = GetExchangeOrderErrorMsgV3(doc);

  return !code || !msg || code.value() != 200 || msg.value() != "ok";
}

bool IsOrderUpdate(const rapidjson::GenericValue<rapidjson::UTF8<>>& json) {
  if (!(json.HasMember("topic") && json.HasMember("op"))) {
    return false;
  }

  return (
      std::strncmp(json["topic"].GetString(), "orders", 6) == 0 &&
      std::strncmp(json["op"].GetString(), "notify", 6) == 0);
}

}  // namespace

void HuobiFuturesParser::Prepare() {
  for (auto* prod : symcache_->GetProducts()) {
    if (prod->quote().currency() == "USDT" || prod->quote().currency() == "usdt") {
      usdt_ccys_.insert(prod->base().currency() + "-" + prod->quote().currency());
    }
  }
}

AccountBalance HuobiFuturesParser::ParseCrossBalance(const JsonValue& doc) {
  RawBalanceMap raw_balance_map;
  auto success = ParseRawBalance(doc, &raw_balance_map);
  if (!success) {
    return {};
  }
  AccountBalance acc_balance;
  acc_balance.set_exchange("Huobi");
  acc_balance.set_market_type("Futures");
  for (const auto& native_currency : symcache_->GetCurrenciesNativeUpper()) {
    auto* balance = acc_balance.add_each_balance();
    if (raw_balance_map.count(native_currency) == 0) {
      balance->set_total(0);
      balance->set_hold(0);
      balance->set_available(0);
    } else {
      balance->MergeFrom(raw_balance_map.at(native_currency));
    }
    auto* currency = symcache_->GetCurrencyByNative(native_currency);
    balance->set_currency(currency->currency());
    balance->set_currency_native(currency->native_currency());
    DLOG(INFO) << "BALANCE: " << currency->native_currency() << " " << balance->total() << " "
               << balance->available();
  }
  is_account_balance_initialized_ = true;

  return acc_balance;
}

// just publish delta balance from ws update
AccountBalance HuobiFuturesParser::ParseCrossBalanceFromWs(const JsonValue& doc) {
  if (!is_account_balance_initialized_) {
    return {};
  }
  RawBalanceMap raw_balance_map;
  auto success = ParseRawBalance(doc, &raw_balance_map);
  if (!success) {
    return {};
  }
  AccountBalance acc_balance;
  acc_balance.set_exchange("Huobi");
  acc_balance.set_market_type("Futures");
  for (const auto& native_currency : symcache_->GetCurrenciesNativeUpper()) {
    if (raw_balance_map.count(native_currency) == 0) {
      continue;
    }
    auto* balance = acc_balance.add_each_balance();
    balance->MergeFrom(raw_balance_map.at(native_currency));
    auto* currency = symcache_->GetCurrencyByNative(native_currency);
    balance->set_currency(currency->currency());
    balance->set_currency_native(currency->native_currency());
    DLOG(INFO) << "BALANCE: " << currency->native_currency() << " " << balance->total() << " "
               << balance->available();
  }

  return acc_balance;
}

AccountBalance HuobiFuturesParser::ParseLinearBalance(const JsonValue& doc) {
  RawBalanceMap raw_balance_map;
  auto success = ParseRawBalance(doc, &raw_balance_map);
  if (!success) {
    return {};
  }

  usdt_cache_map_.clear();
  for (const auto& [native_symbol, raw_balance] : raw_balance_map) {
    const auto& currency = raw_balance.currency();
    if (usdt_cache_map_.count(currency) == 0) {
      usdt_cache_map_.emplace(currency, CurrencyBalance{});
    }
    auto& balance = usdt_cache_map_.at(currency);
    balance.set_total(balance.total() + raw_balance.total());
    balance.set_hold(balance.hold() + raw_balance.hold());
    balance.set_available(balance.available() + raw_balance.available());
  }

  AccountBalance acc_balance;
  acc_balance.set_exchange("Huobi");
  acc_balance.set_market_type("Futures");
  for (const auto& native_currency : symcache_->GetCurrenciesNativeUpper()) {
    auto* balance = acc_balance.add_each_balance();
    if (usdt_cache_map_.count(native_currency) == 0) {
      balance->set_total(0);
      balance->set_hold(0);
      balance->set_available(0);
    } else {
      balance->MergeFrom(usdt_cache_map_.at(native_currency));
    }
    auto* currency = symcache_->GetCurrencyByNative(native_currency);
    balance->set_currency(currency->currency());
    balance->set_currency_native(currency->native_currency());
    DLOG(INFO) << "BALANCE: " << currency->native_currency() << " " << balance->total() << " "
               << balance->available();
  }
  is_account_balance_initialized_ = true;

  return acc_balance;
}

// just publish delta balance from ws update
AccountBalance HuobiFuturesParser::ParseLinearBalanceFromWs(const JsonValue& doc) {
  if (!is_account_balance_initialized_) {
    return {};
  }

  RawBalanceMap raw_balance_map;
  auto success = ParseRawBalance(doc, &raw_balance_map);
  if (!success) {
    return {};
  }

  usdt_cache_map_.clear();
  for (const auto& [native_symbol, raw_balance] : raw_balance_map) {
    const auto& currency = raw_balance.currency();
    if (usdt_cache_map_.count(currency) == 0) {
      usdt_cache_map_.emplace(currency, CurrencyBalance{});
    }
    auto& balance = usdt_cache_map_.at(currency);
    balance.set_total(balance.total() + raw_balance.total());
    balance.set_hold(balance.hold() + raw_balance.hold());
    balance.set_available(balance.available() + raw_balance.available());
  }

  AccountBalance acc_balance;
  acc_balance.set_exchange("Huobi");
  acc_balance.set_market_type("Futures");
  for (const auto& native_currency : symcache_->GetCurrenciesNativeUpper()) {
    if (usdt_cache_map_.count(native_currency) == 0) {
      continue;
    }
    auto* balance = acc_balance.add_each_balance();
    balance->MergeFrom(usdt_cache_map_.at(native_currency));
    auto* currency = symcache_->GetCurrencyByNative(native_currency);
    balance->set_currency(currency->currency());
    balance->set_currency_native(currency->native_currency());
    DLOG(INFO) << "BALANCE: " << currency->native_currency() << " " << balance->total() << " "
               << balance->available();
  }

  return acc_balance;
}

AccountBalance HuobiFuturesParser::ParseNormalBalance(const JsonValue& doc) {
  RawBalanceMap raw_balance_map;
  auto success = ParseRawBalance(doc, &raw_balance_map);
  if (!success) {
    return {};
  }
  AccountBalance acc_balance;
  acc_balance.set_exchange("Huobi");
  acc_balance.set_market_type("Futures");
  for (const auto& native_currency : symcache_->GetCurrenciesNativeUpper()) {
    auto* balance = acc_balance.add_each_balance();
    if (raw_balance_map.count(native_currency) == 0) {
      balance->set_total(0);
      balance->set_hold(0);
      balance->set_available(0);
    } else {
      balance->MergeFrom(raw_balance_map.at(native_currency));
    }
    auto* currency = symcache_->GetCurrencyByNative(native_currency);
    balance->set_currency(currency->currency());
    balance->set_currency_native(currency->native_currency());
    DLOG(INFO) << "BALANCE: " << currency->native_currency() << " " << balance->total() << " "
               << balance->available();
  }
  is_account_balance_initialized_ = true;

  return acc_balance;
}

// just publish delta balance from ws update
AccountBalance HuobiFuturesParser::ParseNormalBalanceFromWs(const JsonValue& doc) {
  if (!is_account_balance_initialized_) {
    return {};
  }

  RawBalanceMap raw_balance_map;
  auto success = ParseRawBalance(doc, &raw_balance_map);
  if (!success) {
    return {};
  }

  AccountBalance acc_balance;
  acc_balance.set_exchange("Huobi");
  acc_balance.set_market_type("Futures");
  for (const auto& native_currency : symcache_->GetCurrenciesNativeUpper()) {
    if (raw_balance_map.count(native_currency) == 0) {
      continue;
    }
    auto* balance = acc_balance.add_each_balance();
    balance->MergeFrom(raw_balance_map.at(native_currency));
    auto* currency = symcache_->GetCurrencyByNative(native_currency);
    balance->set_currency(currency->currency());
    balance->set_currency_native(currency->native_currency());
    DLOG(INFO) << "BALANCE: " << currency->native_currency() << " " << balance->total() << " "
               << balance->available();
  }

  return acc_balance;
}

// NOTE: when field exchange and market_type are set, which means parsed successfully
AccountBalance HuobiFuturesParser::ParseBalance(const JsonValue& doc) {
  if (is_linear()) {
    if (use_cross_mode_) {
      return ParseCrossBalance(doc);
    } else {
      return ParseLinearBalance(doc);
    }
  } else {
    return ParseNormalBalance(doc);
  }
}

void HuobiFuturesParser::ParseRestAccountBalance() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestAccountBalance] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  auto account_balance = ParseBalance(document);
  if (!account_balance.has_exchange() || !account_balance.has_market_type()) {
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  result_.set_type(MsgType::ACCOUNT_BALANCE);
  *result_.mutable_account_balance() = std::move(account_balance);
}

// for readonly OG
void HuobiFuturesParser::ParseRestAccountCrossBalance() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestAccountBalance] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  RawBalanceMap raw_balance_map;
  auto success = ParseRawBalanceWithCrossMode(document, &raw_balance_map);
  if (!success) {
    return;
  }

  cross_balance_map_cache_.clear();
  for (const auto& [currency, raw_balance] : raw_balance_map) {
    cross_balance_map_cache_.emplace(currency, raw_balance);
  }
  is_cross_balance_initialized_ = true;

  if (!is_non_cross_balance_initialized_) {
    // non-cross balance is not inited
    return;
  }

  AccountBalance acc_balance;
  acc_balance.set_exchange("Huobi");
  acc_balance.set_market_type("Futures");
  for (const auto& native_currency : symcache_->GetCurrenciesNativeUpper()) {
    auto* balance = acc_balance.add_each_balance();
    balance->set_total(0);
    balance->set_hold(0);
    balance->set_available(0);
    if (non_cross_balance_map_cache_.count(native_currency)) {
      auto& tmp = non_cross_balance_map_cache_.at(native_currency);
      balance->set_total(balance->total() + tmp.total());
      balance->set_hold(balance->hold() + tmp.hold());
      balance->set_available(balance->available() + tmp.available());
    }
    if (cross_balance_map_cache_.count(native_currency)) {
      auto& tmp = cross_balance_map_cache_.at(native_currency);
      balance->set_total(balance->total() + tmp.total());
      balance->set_hold(balance->hold() + tmp.hold());
      balance->set_available(balance->available() + tmp.available());
    }
    auto* currency = symcache_->GetCurrencyByNative(native_currency);
    balance->set_currency(currency->currency());
    balance->set_currency_native(currency->native_currency());
    DLOG(INFO) << "BALANCE: " << currency->native_currency() << " " << balance->total() << " "
               << balance->available();
  }

  result_.set_type(MsgType::ACCOUNT_BALANCE);
  *result_.mutable_account_balance() = std::move(acc_balance);
}

// for readonly OG
void HuobiFuturesParser::ParseRestAccountNonCrossBalance() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestAccountBalance] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  RawBalanceMap raw_balance_map;
  auto success = ParseRawBalanceWithLinearMode(document, &raw_balance_map);
  if (!success) {
    return;
  }

  usdt_cache_map_.clear();
  for (const auto& [native_symbol, raw_balance] : raw_balance_map) {
    const auto& currency = raw_balance.currency();
    if (usdt_cache_map_.count(currency) == 0) {
      usdt_cache_map_.emplace(currency, CurrencyBalance{});
    }
    auto& balance = usdt_cache_map_.at(currency);
    balance.set_total(balance.total() + raw_balance.total());
    balance.set_hold(balance.hold() + raw_balance.hold());
    balance.set_available(balance.available() + raw_balance.available());
  }

  non_cross_balance_map_cache_.clear();
  for (const auto& [currency, raw_balance] : usdt_cache_map_) {
    non_cross_balance_map_cache_.emplace(currency, raw_balance);
  }
  is_non_cross_balance_initialized_ = true;

  if (!is_cross_balance_initialized_) {
    // cross balance is not inited
    return;
  }

  AccountBalance acc_balance;
  acc_balance.set_exchange("Huobi");
  acc_balance.set_market_type("Futures");
  for (const auto& native_currency : symcache_->GetCurrenciesNativeUpper()) {
    auto* balance = acc_balance.add_each_balance();
    balance->set_total(0);
    balance->set_hold(0);
    balance->set_available(0);
    if (non_cross_balance_map_cache_.count(native_currency)) {
      auto& tmp = non_cross_balance_map_cache_.at(native_currency);
      balance->set_total(balance->total() + tmp.total());
      balance->set_hold(balance->hold() + tmp.hold());
      balance->set_available(balance->available() + tmp.available());
    }
    if (cross_balance_map_cache_.count(native_currency)) {
      auto& tmp = cross_balance_map_cache_.at(native_currency);
      balance->set_total(balance->total() + tmp.total());
      balance->set_hold(balance->hold() + tmp.hold());
      balance->set_available(balance->available() + tmp.available());
    }
    auto* currency = symcache_->GetCurrencyByNative(native_currency);
    balance->set_currency(currency->currency());
    balance->set_currency_native(currency->native_currency());
    DLOG(INFO) << "BALANCE: " << currency->native_currency() << " " << balance->total() << " "
               << balance->available();
  }

  result_.set_type(MsgType::ACCOUNT_BALANCE);
  *result_.mutable_account_balance() = std::move(acc_balance);
}

bool HuobiFuturesParser::ParseRawBalance(const JsonValue& doc, RawBalanceMap* raw_balance_map) {
  if (is_linear()) {
    if (use_cross_mode_) {
      return ParseRawBalanceWithCrossMode(doc, raw_balance_map);
    } else {
      return ParseRawBalanceWithLinearMode(doc, raw_balance_map);
    }
  } else {
    return ParseRawBalanceWithNormalMode(doc, raw_balance_map);
  }
}

bool HuobiFuturesParser::ParseRawBalanceWithCrossMode(
    const JsonValue& doc,
    RawBalanceMap* raw_balance_map) {
  /*
  {
    "status": "ok",
    "data": [{
      "margin_mode": "cross",
      "margin_account": "USDT",
      "margin_asset": "USDT",
      "margin_balance": 45438.821413635382950726,
      "margin_static": 45181.107613635382950726,
      "margin_position": 18888.942030000000000000,
      "margin_frozen": 0,
      "profit_real": 0E-18,
      "profit_unreal": 257.713800000000000000,
      "withdraw_available": 26292.165583635382950726,
      "risk_rate": 13.948607407107205876,
      "contract_detail": [...],
    "ts": 1609998994789
  }
  */
  if (IsHuobiErrMessage(doc)) {
    LOG(ERROR) << "[ParseBalance] " << JsonToString(doc);
    return false;
  }
  if (!(doc.HasMember("data") && doc.HasMember("ts"))) {
    LOG(ERROR) << "[ParseBalance] no parsable balance data " << JsonToString(doc);
    return false;
  }
  if (!doc["data"].IsArray() || doc["data"].Size() != 1) {
    LOG(ERROR) << "[ParseBalance] not an array! Wrong balance format! " << JsonToString(doc);
    return false;
  }

  auto data = doc["data"][0].GetObject();
  if (!data.HasMember("margin_mode") || std::string(data["margin_mode"].GetString()) != "cross" ||
      !data.HasMember("margin_asset") || std::string(data["margin_asset"].GetString()) != "USDT" ||
      !data.HasMember("margin_balance") || !data.HasMember("profit_unreal") ||
      !data.HasMember("margin_frozen") || !data.HasMember("margin_position")) {
    LOG(ERROR) << "[ParseBalance] failed to data checking. " << JsonToString(doc);
    return false;
  }

  CurrencyBalance balance;
  balance.set_currency_native("USDT");
  double total = stod(data["margin_balance"].GetString());
  double frozen =
      stod(data["margin_frozen"].GetString()) + stod(data["margin_position"].GetString());
  balance.set_total(total);
  balance.set_hold(frozen);
  balance.set_available(total - frozen);
  raw_balance_map->emplace(balance.currency_native(), balance);
  DLOG(INFO) << " usdt balance total: " << total << " frozen: " << frozen;

  return true;
}

bool HuobiFuturesParser::ParseRawBalanceWithLinearMode(
    const JsonValue& doc,
    RawBalanceMap* raw_balance_map) {
  if (IsHuobiErrMessage(doc)) {
    LOG(ERROR) << "[ParseBalance] " << JsonToString(doc);
    return false;
  }
  if (!(doc.HasMember("data") && doc.HasMember("ts"))) {
    LOG(ERROR) << "[ParseBalance] no parsable balance data " << JsonToString(doc);
    return false;
  }
  if (!doc["data"].IsArray()) {
    LOG(ERROR) << "[ParseBalance] not an array! Wrong balance format! " << JsonToString(doc);
    return false;
  }

  for (const auto& data : doc["data"].GetArray()) {
    if (!data.HasMember("symbol") || !data.HasMember("margin_balance") ||
        !data.HasMember("profit_unreal") || !data.HasMember("margin_frozen") ||
        !data.HasMember("margin_available") || !data.HasMember("margin_asset")) {
      continue;
    }

    CurrencyBalance balance;
    balance.set_currency(data["margin_asset"].GetString());
    balance.set_currency_native(data["symbol"].GetString());
    balance.set_total(stod(data["margin_balance"].GetString()));
    balance.set_hold(stod(data["margin_frozen"].GetString()));
    balance.set_available(stod(data["margin_available"].GetString()));

    raw_balance_map->emplace(balance.currency_native(), balance);
  }

  return true;
}

bool HuobiFuturesParser::ParseRawBalanceWithNormalMode(
    const JsonValue& doc,
    RawBalanceMap* raw_balance_map) {
  if (IsHuobiErrMessage(doc)) {
    LOG(ERROR) << "[ParseBalance] " << JsonToString(doc);
    return false;
  }
  if (!(doc.HasMember("data") && doc.HasMember("ts"))) {
    LOG(ERROR) << "[ParseBalance] no parsable balance data " << JsonToString(doc);
    return false;
  }
  if (!doc["data"].IsArray()) {
    LOG(ERROR) << "[ParseBalance] not an array! Wrong balance format! " << JsonToString(doc);
    return false;
  }

  for (const auto& data : doc["data"].GetArray()) {
    if (!data.HasMember("symbol") || !data.HasMember("margin_balance") ||
        !data.HasMember("profit_unreal") || !data.HasMember("margin_frozen") ||
        !data.HasMember("margin_available")) {
      continue;
    }
    CurrencyBalance balance;
    balance.set_currency_native(data["symbol"].GetString());
    balance.set_total(stod(data["margin_balance"].GetString()));
    balance.set_hold(stod(data["margin_frozen"].GetString()));
    balance.set_available(stod(data["margin_available"].GetString()));
    raw_balance_map->emplace(balance.currency_native(), balance);
  }

  return true;
}

void HuobiFuturesParser::ParseRestAccountPosition() {
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseRestAccountPosition] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  auto account_position = ParsePosition(doc);
  if (!account_position.has_exchange() || !account_position.has_market_type()) {
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  result_.set_type(MsgType::ACCOUNT_POSITION);
  *result_.mutable_account_position() = std::move(account_position);
}

void HuobiFuturesParser::ParseRestAccountNonCrossPosition() {
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseRestAccountNonCrossPosition] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  RawPositionMap raw_position_map;
  auto success = ParseRawPosition(doc, &raw_position_map);
  if (!success) {
    return;
  }

  non_cross_position_map_cache_ = std::move(raw_position_map);
  is_non_cross_position_initialized_ = true;

  if (!is_cross_position_initialized_) {
    return;
  }

  ParseRestCrossAndNonCrossPosition();
}

void HuobiFuturesParser::ParseRestAccountCrossPosition() {
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseRestAccountCrossPosition] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  RawPositionMap raw_position_map;
  auto success = ParseRawPosition(doc, &raw_position_map);
  if (!success) {
    return;
  }

  cross_position_map_cache_ = std::move(raw_position_map);
  is_cross_position_initialized_ = true;

  if (!is_non_cross_position_initialized_) {
    return;
  }

  ParseRestCrossAndNonCrossPosition();
}

void HuobiFuturesParser::ParseRestCrossAndNonCrossPosition() {
  CHECK(is_cross_position_initialized_ && is_non_cross_position_initialized_);

  AccountPosition acc_position;
  acc_position.set_exchange("Huobi");
  acc_position.set_market_type("Futures");
  for (const auto& native_symbol : symcache_->GetProductNative()) {
    if (non_cross_position_map_cache_.count(native_symbol) != 0 ||
        cross_position_map_cache_.count(native_symbol) != 0) {
      continue;
    }
    auto* position = acc_position.add_each_position();
    position->set_long_position(0);
    position->set_short_position(0);
    position->set_net_position(0);
    position->set_available_long_position(0);
    position->set_available_short_position(0);
    position->set_native_symbol(native_symbol);

    DLOG(INFO) << "POS: " << native_symbol << " " << position->net_position();
  }

  RawPositionMap merged_map(non_cross_position_map_cache_);

  for (const auto& pair : cross_position_map_cache_) {
    const auto& tmp = pair.second;
    auto& position = merged_map[pair.first];
    position.set_long_position(position.long_position() + tmp.long_position());
    position.set_short_position(position.short_position() + tmp.short_position());
    position.set_net_position(position.net_position() + tmp.net_position());
    position.set_available_long_position(
        position.available_long_position() + tmp.available_long_position());
    position.set_available_short_position(
        position.available_short_position() + tmp.available_short_position());
  }

  for (auto& pair : merged_map) {
    auto* position = acc_position.add_each_position();
    *position = std::move(pair.second);
    position->set_native_symbol(pair.first);
    DLOG(INFO) << "POS: " << pair.first << " " << position->net_position();
  }

  result_.set_type(MsgType::ACCOUNT_POSITION);
  *result_.mutable_account_position() = std::move(acc_position);
}

// NOTE: when field exchange and market_type are set, which means parsed successfully
AccountPosition HuobiFuturesParser::ParsePosition(const JsonValue& doc) {
  RawPositionMap raw_position_map;
  auto success = ParseRawPosition(doc, &raw_position_map);
  if (!success) {
    return {};
  }

  AccountPosition acc_position;
  acc_position.set_exchange("Huobi");
  acc_position.set_market_type("Futures");
  for (const auto& native_symbol : symcache_->GetProductNative()) {
    if (raw_position_map.count(native_symbol) != 0) {
      continue;
    }
    auto* position = acc_position.add_each_position();
    position->set_long_position(0);
    position->set_short_position(0);
    position->set_net_position(0);
    position->set_available_long_position(0);
    position->set_available_short_position(0);
    position->set_native_symbol(native_symbol);

    DLOG(INFO) << "POS: " << native_symbol << " " << position->net_position();
  }

  for (auto& pair : raw_position_map) {
    auto* position = acc_position.add_each_position();
    *position = std::move(pair.second);
    position->set_native_symbol(pair.first);
    DLOG(INFO) << "POS: " << pair.first << " " << position->net_position();
  }
  is_account_position_initialized_ = true;

  return acc_position;
}

bool HuobiFuturesParser::ParseRawPosition(const JsonValue& doc, RawPositionMap* raw_position_map) {
  if (IsHuobiErrMessage(doc)) {
    LOG(ERROR) << "[ParsePosition] " << JsonToString(doc);
    return false;
  }
  if (!(doc.HasMember("data") && doc.HasMember("ts"))) {
    LOG(ERROR) << "[ParsePosition] no parsable position data " << JsonToString(doc);
    return false;
  }
  if (!doc["data"].IsArray()) {
    LOG(ERROR) << "[ParsePosition] no parsable position data " << JsonToString(doc);
    return false;
  }

  for (const auto& data : doc["data"].GetArray()) {
    if (!data.HasMember("contract_code") || !data.HasMember("direction") ||
        !data.HasMember("volume") || !data.HasMember("available")) {
      continue;
    }

    auto native_symbol = data["contract_code"].GetString();
    auto& position = (*raw_position_map)[native_symbol];
    position.set_native_symbol(native_symbol);
    auto volume = stod(data["volume"].GetString());
    auto available = stod(data["available"].GetString());
    std::string_view side = data["direction"].GetString();
    if (side == "buy") {
      position.set_long_position(volume);
      position.set_available_long_position(available);
    } else if (side == "sell") {
      position.set_short_position(-volume);
      position.set_available_short_position(-available);
    } else {
      THROW() << "Invalid direction! " << side;
    }
    position.set_net_position(position.long_position() + position.short_position());
  }

  return true;
}

ProductOrderElement HuobiFuturesParser::ParseOrderInfo(
    const JsonValue& data,
    const std::string& version) {
  ProductOrderElement order;
  order.set_market_type(MarketType::Futures);
  order.set_exchange_type(ExchangeType::Huobi);
  order.set_api_version(version);
  order.set_exchange_order_id(GetExchangeOrderId(data));
  order.set_symbol(data["contract_code"].GetString());
  order.set_price(stod(data["price"].GetString()));
  order.set_qty(std::stoi(data["volume"].GetString()));
  order.set_proc_order_id(GetProcOrderId(data));
  order.set_side(GetOrderSide(data));
  order.set_order_type(GetOrderType());
  const int status = std::stoi(data["status"].GetString());

  // 1: prepare-to-submit
  // 2: prepare-to-submit (same as 1)
  // 3: submitted
  // 4: partially filled
  // 5: partially filled and canceled
  // 6: fullly filled
  // 7: canceled
  // 11: in the process of cancelling
  switch (status) {
    case 1:
    case 11:
    case 2: {
      order.set_is_live(false);
      break;
    }
    case 3:
    case 4: {
      order.set_is_live(true);
      break;
    }
    case 5:
    case 7: {
      order.set_cancel_confirmed(true);
      break;
    }
    case 6: {
      order.set_fully_filled(true);
      break;
    }
    default: {
      LOG(ERROR) << "Unknown order status! " << status;
      break;
    }
  }
  return order;
}

void HuobiFuturesParser::ParseRestOpenOrdersInfo() {
  JsonDocument data;
  if (!PrepareJsonDocument(&data)) {
    LOG(ERROR) << "[ParseRestOpenOrdersInfo] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  if (IsHuobiErrMessage(data)) {
    LOG(ERROR) << "[ParseRestOpenOrdersInfo] Error. " << JsonToString(data);
    return;
  }

  if (!(data.HasMember("data") && data["data"].HasMember("orders"))) {
    LOG(ERROR) << "Not .data.orders in json. " << JsonToString(data);
    return;
  }

  const auto& info_list = data["data"]["orders"];
  if (!info_list.IsArray()) {
    LOG(ERROR) << "Unexpected order list format!" << JsonToString(data);
    return;
  }

  auto product_order_bundle = result_.mutable_order_list();
  product_order_bundle->Clear();
  bool product_order_bundle_found = false;
  for (const auto& info : info_list.GetArray()) {
    auto* order = product_order_bundle->add_each_order();
    auto new_order = ParseOrderInfo(info);
    order->MergeFrom(new_order);
    order->set_timestamp(msg_->timestamp);
    product_order_bundle_found = true;
  }
  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

void HuobiFuturesParser::ParseRestCancelOrdersInfo() {
  JsonDocument data;
  if (!PrepareJsonDocument(&data)) {
    LOG(ERROR) << "[ParseRestCancelOrdersInfo] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  if (IsHuobiErrMessageV3(data)) {
    LOG(ERROR) << "[ParseRestCancelOrdersInfo] Error. " << JsonToString(data);
    return;
  }

  if (!data.HasMember("data")) {
    LOG(ERROR) << "Not .data in json. " << JsonToString(data);
    return;
  }

  const auto& info_list = data["data"];
  if (!info_list.IsArray()) {
    LOG(ERROR) << "Unexpected order list format!" << JsonToString(data);
    return;
  }

  auto product_order_bundle = result_.mutable_order_list();
  product_order_bundle->Clear();
  bool product_order_bundle_found = false;
  for (const auto& info : info_list.GetArray()) {
    auto* order = product_order_bundle->add_each_order();
    auto new_order = ParseOrderInfo(info, "v3");
    order->MergeFrom(new_order);
    order->set_timestamp(msg_->timestamp);
    product_order_bundle_found = true;
  }
  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

ProductOrderBundle HuobiFuturesParser::ParseWsOrderInfoListV2(const JsonValue& data) {
  ProductOrderBundle product_order_bundle;
  auto* order = product_order_bundle.add_each_order();
  auto new_order = ParseOrderInfo(data);
  order->MergeFrom(new_order);
  order->set_timestamp(msg_->timestamp);
  return product_order_bundle;
}

ProductFillBundle HuobiFuturesParser::ParseWsFillInfoListV2(const JsonValue& data) {
  /*
   * Each trade msg:
   *
   * {"id":"40758678286-669615698982084608-1","trade_id":40758678286,"trade_volume":2,"trade_price":3.728,"trade_fee":0.000697424892703862,"trade_turnover":20.000000000000000000,"created_at":1579690428493,"fee_asset":"EOS","role":"maker"}
   */

  ProductFillBundle product_fill_bundle;
  if (!data.HasMember("trade")) {
    return product_fill_bundle;
  }

  const auto& info_list = data["trade"];
  if (info_list.Size() == 0) {
    return product_fill_bundle;
  }

  auto timestamp = msg_->timestamp;
  std::string native_symbol = data["contract_code"].GetString();
  std::string exchange_order_id = GetExchangeOrderId(data);
  const auto proc_order_id = GetProcOrderId(data);
  for (const auto& info : info_list.GetArray()) {
    auto* fill = product_fill_bundle.add_each_fill();
    fill->set_symbol(native_symbol);
    fill->set_fill_timestamp(timestamp);
    fill->set_exchange_order_id(exchange_order_id);
    if (proc_order_id != 0) {
      fill->set_proc_order_id(proc_order_id);
    }
    fill->set_price(stod(info["trade_price"].GetString()));
    fill->set_qty(stod(info["trade_volume"].GetString()));
    fill->set_fill_id(GetFillId(info["id"].GetString(), info["trade_id"].GetString()));
    fill->set_fill_type(GetFillType(info["role"].GetString()));
    fill->set_fee(-stod(info["trade_fee"].GetString()));
    fill->set_fee_currency(info["fee_asset"].GetString());
  }
  return product_fill_bundle;
}

void HuobiFuturesParser::ParseRestFillInfoList() {
  JsonDocument data;
  if (!PrepareJsonDocument(&data)) {
    LOG(ERROR) << "[ParseRestFillInfoList] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  if (IsHuobiErrMessageV3(data)) {
    LOG(ERROR) << "[ParseRestFillInfoList] " << JsonToString(data);
    return;
  }
  if (!data.HasMember("data")) {
    LOG(ERROR) << "Not .data in json. " << JsonToString(data);
    return;
  }

  const auto& info_list = data["data"];
  if (info_list.Size() == 0) {
    return;
  }
  auto product_fill_bundle = result_.mutable_fill_list();
  product_fill_bundle->Clear();
  bool product_fill_bundle_found = false;
  for (const auto& info : info_list.GetArray()) {
    if (info["id"].IsNull()) {
      LOG(ERROR) << "fill is ignored due to invalid fill id, id=null" << JsonToString(data);
      continue;
    }
    auto* fill = product_fill_bundle->add_each_fill();
    fill->set_symbol(info["contract_code"].GetString());
    fill->set_exchange_order_id(info["order_id_str"].GetString());
    fill->set_proc_order_id(GetProcOrderId(info));
    fill->set_price(stod(info["trade_price"].GetString()));
    fill->set_qty(stod(info["trade_volume"].GetString()));
    fill->set_fill_id(GetFillId(info["id"].GetString(), info["match_id"].GetString()));
    fill->set_fill_type(GetFillType(info["role"].GetString()));
    fill->set_fee(-stod(info["trade_fee"].GetString()));
    fill->set_fee_currency(info["fee_asset"].GetString());
    product_fill_bundle_found = true;
  }
  if (product_fill_bundle_found) {
    result_.set_type(MsgType::ORDER_FILL);
  }
}

void HuobiFuturesParser::ParseSubmitResponse() {
  /*
   *
   * {"status":"error","err_code":"1014","err_msg":"This contract doesnt
   * exist.","ts":"1579591070924"}
   *
   */
  const auto order_context = msg_->oc;
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::SUBMIT_RESPONSE);
  auto resp = result_.mutable_submit_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(static_cast<int32_t>(http_context->res.base().result()));
  resp->set_timestamp(msg_->timestamp);

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "Submit Timeout: " << order_context->proc_order_id();
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    resp->set_success(false);
    return;
  }

  if (http_context->res.result_int() == 429) {
    LOG(ERROR) << "[HuobiFuturesSubmitOrder] API_LIMIT_ERROR";
    resp->set_error_code(coin::proto::OrderErrorCode::API_LIMIT_ERROR);
    resp->set_success(false);
    return;
  }

  JsonDocument json;
  if (!PrepareJsonDocument(&json)) {
    LOG(ERROR) << "[HuobiFuturesSubmitOrder] Parse Error\n" << http_context->res;
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    resp->set_success(false);
    return;
  }

  if (json.HasMember("err_code")) {
    LOG(ERROR) << JsonToString(json);
    auto error_code = GetOrderErrorCode(json["err_code"]);
    resp->set_error_code(error_code);
    resp->set_exchange_error_code(GetExchangeOrderErrorCode(json));
    resp->set_exchange_error_msg(GetExchangeOrderErrorMsg(json));
    if (error_code != coin::proto::OrderErrorCode::ORDER_NO_ERROR) {
      resp->set_success(false);
      return;
    }
  }

  CHECK_THROW(json.HasMember("status")) << http_context->res;
  if (std::string(json["status"].GetString()) != "ok") {
    LOG(ERROR) << "[HuobiFuturesSubmitOrder] failed. \n" << http_context->res;
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_UNKNOWN_ERROR);
    resp->set_success(false);
    return;
  }
  resp->set_success(true);
  resp->set_exchange_order_id(json["data"]["order_id_str"].GetString());
}

void HuobiFuturesParser::ParseBatchSubmitResponse() {
  /*
   * {
   *     "status":"ok",
   *     "data":{
   *         "errors":[
   *             {
   *                 "index":2,
   *                 "err_code":1050,
   *                 "err_msg":"Customers order number is repeated. Please try again later."
   *             }
   *         ],
   *         "success":[
   *             {
   *                 "order_id":770323847022211072,
   *                 "client_order_id":57012021024,
   *                 "index":1,
   *                 "order_id_str":"770323847022211072"
   *             }
   *         ]
   *     },
   *     "ts":1603701117058
   * }
   */
  const auto& oc_list = msg_->oc_list;
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::MULTI_SUBMIT_RESPONSE);
  auto* response = result_.mutable_multi_submit_response();
  response->Clear();

  for (const auto* order_context : oc_list) {
    auto* resp = response->add_each_submit_response();

    resp->set_symbol(order_context->order_spec().product);
    resp->set_proc_order_id(order_context->proc_order_id());
    resp->set_http_status(static_cast<int32_t>(http_context->res.base().result()));
    resp->set_timestamp(msg_->timestamp);
    resp->set_success(false);
  }

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "[" << __func__ << "] AsioHttpContextStatus Error";
    for (auto& resp : *(response->mutable_each_submit_response())) {
      resp.set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    }
    return;
  }

  if (http_context->res.result_int() == 429) {
    LOG(ERROR) << "[" << __func__ << "] API_LIMIT_ERROR";
    for (auto& resp : *(response->mutable_each_submit_response())) {
      resp.set_error_code(coin::proto::OrderErrorCode::API_LIMIT_ERROR);
    }
    return;
  }

  JsonDocument json;
  if (!PrepareJsonDocument(&json) || !json.IsObject() || !json.HasMember("data") ||
      !json["data"].IsObject()) {
    LOG(ERROR) << "[" << __func__ << "] Parse Error\n" << http_context->res;
    for (auto& resp : *(response->mutable_each_submit_response())) {
      resp.set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    }
    return;
  }

  CHECK_THROW(json.HasMember("status")) << http_context->res;
  if (std::string(json["status"].GetString()) != "ok") {
    LOG(ERROR) << "[" << __func__ << "] failed. \n" << http_context->res;
    for (auto& resp : *(response->mutable_each_submit_response())) {
      resp.set_error_code(coin::proto::OrderErrorCode::ORDER_UNKNOWN_ERROR);
    }
    return;
  }

  const auto& data = json["data"];
  std::unordered_map<int64_t, const JsonValue*> index_to_response_map;
  if (data.HasMember("success") && data["success"].IsArray()) {
    ParseBatchResponse(&index_to_response_map, data["success"]);
  }
  if (data.HasMember("errors") && data["errors"].IsArray()) {
    ParseBatchResponse(&index_to_response_map, data["errors"]);
  }

  CHECK_EQ(index_to_response_map.size(), response->each_submit_response_size())
      << "Batch submit response size not equal to request size response:" << JsonToString(json)
      << " request:" << response->DebugString();
  for (int i = 0; i < response->each_submit_response_size(); ++i) {
    int index = i + 1;
    CHECK(index_to_response_map.count(index) != 0)
        << "have no index:" << index << " in response:" << JsonToString(json);
    const auto& data = *index_to_response_map[index];
    auto* resp = response->mutable_each_submit_response(i);

    if (data.HasMember("err_code")) {
      LOG(ERROR) << JsonToString(data);
      auto error_code = GetOrderErrorCode(data["err_code"]);
      resp->set_error_code(error_code);
      resp->set_exchange_error_code(GetExchangeOrderErrorCode(data));
      resp->set_exchange_error_msg(GetExchangeOrderErrorMsg(data));
      if (error_code != coin::proto::OrderErrorCode::ORDER_NO_ERROR) {
        continue;
      }
    }

    resp->set_success(true);
    resp->set_exchange_order_id(data["order_id_str"].GetString());
  }
}

void HuobiFuturesParser::ParseCancelResponse() {
  /*
   * Response:
   * {"status":"ok","data":{"errors":[],"successes":"669116322488262656"},"ts":1579571632308}
   *
   * {"status":"error","err_code":"1014","err_msg":"This contract doesnt
   * exist.","ts":"1579590413927"}
   *
   * {"status":"error","err_code":1032,"err_msg":"The number of access exceeded the
   * limit.","ts":1582700042071}
   *
   * {"status":"ok","data":{"errors":
   * [{"order_id":"1593763106626075169","err_code":1061,"err_msg":"This order doesnt exist."}]
   * ,"successes":""},"ts":1593763122745}
   *
   */
  const auto order_context = msg_->oc;
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::CANCEL_RESPONSE);
  auto resp = result_.mutable_cancel_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(static_cast<int32_t>(http_context->res.base().result()));
  resp->set_exchange_order_id(order_context->exchange_order_id());
  resp->set_timestamp(msg_->timestamp);

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "Cancel error!" << order_context->proc_order_id();
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    resp->set_success(false);
    return;
  }

  if (http_context->res.result_int() == 429) {
    LOG(ERROR) << "[HuobiFuturesCancelOrder] API_LIMIT_ERROR";
    resp->set_error_code(coin::proto::OrderErrorCode::API_LIMIT_ERROR);
    resp->set_success(false);
    return;
  }

  JsonDocument json;
  if (!PrepareJsonDocument(&json)) {
    LOG(ERROR) << "[HuobiFuturesCancelOrder] RESPONSE_ERROR" << http_context->res;
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    resp->set_success(false);
    return;
  }

  CHECK_THROW(json.HasMember("status")) << http_context->res;
  if (std::string(json["status"].GetString()) != "ok") {
    resp->set_error_code(GetOrderErrorCode(json["err_code"]));
    resp->set_exchange_error_code(GetExchangeOrderErrorCode(json));
    resp->set_exchange_error_msg(GetExchangeOrderErrorMsg(json));
    resp->set_success(false);
    return;
  }

  if (json.HasMember("data") && json["data"].HasMember("errors") &&
      json["data"]["errors"].IsArray() && json["data"]["errors"].Size() > 0) {
    LOG(ERROR) << "[HuobiFuturesCancelOrder] UNCOMMON ERROR FORMAT " << JsonToString(json);
    auto info = json["data"]["errors"][0].GetObject();
    if (info.HasMember("err_code")) {
      resp->set_error_code(GetOrderErrorCode(info["err_code"]));
    } else {
      resp->set_error_code(OrderErrorCode::ORDER_UNKNOWN_ERROR);
    }
    resp->set_exchange_error_code(GetExchangeOrderErrorCode(info));
    resp->set_exchange_error_msg(GetExchangeOrderErrorMsg(info));
    resp->set_success(false);
    return;
  }

  // in hbdm, we should query additional in order to be confirmed.
  if (!json["data"].HasMember("successes")) {
    LOG(ERROR) << "[HuobiFuturesCancelOrder] failed. \n" << http_context->res;
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_UNKNOWN_ERROR);
    resp->set_success(false);
    return;
  }
  CHECK_THROW(json["data"]["successes"].IsString());
  resp->set_success(true);
}

void HuobiFuturesParser::ParseRestTransfer() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestTransfer] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  auto currency_transfer_bundle = ParseTransfer(document);
  if (!currency_transfer_bundle.has_exchange() || !currency_transfer_bundle.has_market_type()) {
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  result_.set_type(MsgType::TRANSFER);
  *result_.mutable_currency_transfer_bundle() = std::move(currency_transfer_bundle);
}

CurrencyTransferBundle HuobiFuturesParser::ParseTransfer(const JsonValue& doc) {
  /*
  {
      "status": "ok",
      "data": {
          "total_page": 15,
          "current_page": 1,
          "total_size": 15,
          "financial_record": [
              {
                  "id": 3662498355,
                  "symbol": "ADA",
                  "type": 8,
                  "amount": -0.074766355140186915,
                  "ts": 1605014144415,
                  "contract_code": "ADA201225"
              }
          ]
      },
      "ts": 1604306015124
  }
  */
  CurrencyTransferBundle currency_transfer_bundle;

  if (IsHuobiErrMessageV3(doc)) {
    LOG(ERROR) << "[ParseTransfer] Error message" << JsonToString(doc);
    return currency_transfer_bundle;
  }

  if (!doc.HasMember("data") || !doc["data"].IsArray()) {
    LOG(ERROR) << "[ParseTransfer] Invalid data format" << JsonToString(doc);
    return currency_transfer_bundle;
  }

  currency_transfer_bundle.set_exchange("Huobi");
  currency_transfer_bundle.set_market_type("Futures");
  for (const auto& data : doc["data"].GetArray()) {
    if (!data.HasMember("symbol") || !data.HasMember("type") || !data.HasMember("amount")) {
      LOG(ERROR) << "[ParseRestTransfer] Invalid transfer format";
      continue;
    }
    if (!symcache_->has_currency_native(data["symbol"].GetString())) {
      continue;
    }
    auto* currency = symcache_->GetCurrencyByNative(data["symbol"].GetString());
    auto* currency_transfer = currency_transfer_bundle.add_each_transfer();
    currency_transfer->set_currency(currency->currency());
    std::string type = data["type"].GetString();
    if (type == "14") {
      currency_transfer->set_type(TransferRequestProto::WITHDRAW_SPOT);
    } else if (type == "15") {
      currency_transfer->set_type(TransferRequestProto::DEPOSIT_SPOT);
    } else if (type == "34") {
      currency_transfer->set_type(TransferRequestProto::DEPOSIT_SUBACCOUNT);
    } else if (type == "35") {
      currency_transfer->set_type(TransferRequestProto::WITHDRAW_SUBACCOUNT);
    } else if (type == "35") {
      currency_transfer->set_type(TransferRequestProto::WITHDRAW);
    } else if (type == "36") {
      currency_transfer->set_type(TransferRequestProto::DEPOSIT);
    }
    currency_transfer->set_status(TransferRequestProto::TRANSFER_CONFIRMED);
    currency_transfer->set_transfer_id(data["id"].GetString());
    currency_transfer->set_qty(stod(data["amount"].GetString()));
    currency_transfer->set_create_timestamp(stoll(data["ts"].GetString()) * 1e6);
  }
  return currency_transfer_bundle;
}

void HuobiFuturesParser::ParseWsMessage() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseWsMessage] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  if (document.HasMember("op") && document.HasMember("topic") &&
      document["op"].GetString() == std::string("notify")) {
    if (is_readonly_order_system_) {
      // ignore balance/position from ws push for readonly og first
      return;
    }
    const std::string& native_topic = document["topic"].GetString();
    if (native_topic == "accounts") {
      AccountBalance acc_balance;
      if (is_linear()) {
        acc_balance = ParseLinearBalanceFromWs(document);
      } else {
        acc_balance = ParseNormalBalanceFromWs(document);
      }
      if (!acc_balance.has_exchange() || !acc_balance.has_market_type()) {
        result_.set_error_code(OrderErrorCode::INVALID_JSON);
        return;
      }
      result_.set_type(MsgType::ACCOUNT_BALANCE);
      *result_.mutable_account_balance() = std::move(acc_balance);
    } else if (native_topic == "positions") {
      // TODO(taekwon): disabled this due to wrong working
      // UpdateAccountPosition(parser_t()->ParsePosition(document));
    } else if (native_topic == "accounts_cross") {
      auto acc_balance = ParseCrossBalanceFromWs(document);
      if (!acc_balance.has_exchange() || !acc_balance.has_market_type()) {
        result_.set_error_code(OrderErrorCode::INVALID_JSON);
        return;
      }
      result_.set_type(MsgType::ACCOUNT_BALANCE);
      *result_.mutable_account_balance() = std::move(acc_balance);
    }
  }

  if (IsOrderUpdate(document)) {
    *result_.mutable_fill_list() = ParseWsFillInfoListV2(document);
    *result_.mutable_order_list() = ParseWsOrderInfoListV2(document);
    result_.set_type(MsgType::ORDER_FILL);
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

auto HuobiFuturesParser::ParseRawMessage(const RawMessage& msg) -> const ParseResult& {
  result_.Clear();
  msg_ = &msg;
  sub_topic_id_ = static_cast<OrderSubTopicId>(msg.type);

  if (msg.type == OrderSubTopicId::WS_MESSAGE) {
    ParseWsMessage();
    return result_;
  }

  switch (msg.http_context->query_type) {
    case OrderSubTopicId::SUBMIT_RESPONSE:
      ParseSubmitResponse();
      break;
    case OrderSubTopicId::CANCEL_RESPONSE:
      ParseCancelResponse();
      break;
    case OrderSubTopicId::MULTI_CANCEL_RESPONSE:
      // pass
      break;
    case OrderSubTopicId::MULTI_SUBMIT_RESPONSE:
      ParseBatchSubmitResponse();
      break;
    case OrderSubTopicId::REST_OPEN_ORDERS:
      ParseRestOpenOrdersInfo();
      break;
    case OrderSubTopicId::REST_CANCEL_ORDERS:
      ParseRestCancelOrdersInfo();
      break;
    case OrderSubTopicId::REST_FILL_ORDERS:
      ParseRestFillInfoList();
      break;
    case OrderSubTopicId::REST_ACCOUNT_BALANCE:
      ParseRestAccountBalance();
      break;
    case OrderSubTopicId::REST_ACCOUNT_CROSS_BALANCE:
      ParseRestAccountCrossBalance();
      break;
    case OrderSubTopicId::REST_ACCOUNT_NON_CROSS_BALANCE:
      ParseRestAccountNonCrossBalance();
      break;
    case OrderSubTopicId::REST_ACCOUNT_POSITION:
      ParseRestAccountPosition();
      break;
    case OrderSubTopicId::TRANSFER:
      ParseRestTransfer();
      break;
    case OrderSubTopicId::REST_ACCOUNT_CROSS_POSITION:
      ParseRestAccountCrossPosition();
      break;
    case OrderSubTopicId::REST_ACCOUNT_NON_CROSS_POSITION:
      ParseRestAccountNonCrossPosition();
      break;
    default: {
      LOG(ERROR) << "Parsing Error. " << msg_->DebugString();
      result_.set_error_code(OrderErrorCode::ORDER_UNKNOWN_ERROR);
      break;
    }
  }

  return result_;
}

}  // namespace coin2::exchange::huobi_futures::order_v1
